# models.py
import logging

from django.db import models
from django.utils import timezone
from django.conf import settings
from apscheduler.schedulers.background import BackgroundScheduler
from django_apscheduler.jobstores import DjangoJobStore
from apscheduler.jobstores.base import JobLookupError
from functools import wraps

# 全局任务注册器
class TaskRegistry:
    tasks = {}

    @classmethod
    def register(cls, display_name):
        """注册任务装饰器"""
        def decorator(func):
            cls.tasks[func.__module__ + '.' + func.__name__] = display_name
            return func

        return decorator

    @classmethod
    def get_registered_tasks(cls):
        """获取已注册任务"""
        return cls.tasks

    @classmethod
    def choices(cls):
        return [(key, value) for key, value in cls.tasks.items()]


class ScheduledTask(models.Model):
    SCHEDULE_TYPES = (
        ('date', '一次性任务'),
        ('interval', '间隔任务'),
        ('cron', '定时任务'),
    )

    name = models.CharField('任务名称', max_length=100)
    task_function = models.CharField(
        '执行函数',
        max_length=200,
        choices=TaskRegistry.choices,  # 从注册器中动态获取选项
        help_text='从注册的任务函数中选择'
    )
    args = models.JSONField('位置参数', default=list, blank=True)
    kwargs = models.JSONField('关键字参数', default=dict, blank=True)
    schedule_type = models.CharField('调度类型', max_length=10, choices=SCHEDULE_TYPES)
    cron = models.CharField('Cron表达式', max_length=50, blank=True, help_text='例如: * * * * * (分 时 日 月 周)')
    interval = models.DurationField('间隔时间', null=True, blank=True)
    start_time = models.DateTimeField('开始时间', default=timezone.now)
    is_active = models.BooleanField('启用任务', default=True)
    next_run = models.DateTimeField('下次执行', null=True, blank=True)

    def __str__(self):
        return f"{self.name} ({self.get_schedule_type_display()})"

    # 检查start_time是否早于当前时间
    def save(self, *args, **kwargs):
        if self.schedule_type == 'date' and self.start_time < timezone.now():
            raise ValueError("一次性任务的开始时间必须晚于当前时间")
        if self.schedule_type == 'interval' and self.next_run:
            raise ValueError("间隔任务的下次执行时间必须为空")
        if self.schedule_type == 'cron' and not self.cron:
            raise ValueError("定时任务的Cron表达式不能为空")
        if self.schedule_type == 'cron' and self.start_time < timezone.now():
            raise ValueError("定时任务的开始时间必须晚于当前时间")
        super().save(*args, **kwargs)
        # 立即同步任务到调度器
        from django.db import transaction
        transaction.on_commit(lambda: self.sync_with_scheduler())

    def delete(self, *args, **kwargs):
        from django.db import transaction
        # 先保存任务ID用于后续清理
        job_id = str(self.id)
        super().delete(*args, **kwargs)
        # 事务提交后再执行移除操作
        transaction.on_commit(lambda: self.__class__.post_delete_cleanup(job_id))
    @classmethod
    def post_delete_cleanup(cls, job_id):
        scheduler = get_scheduler()
        try:
            scheduler.remove_job(job_id)
        except JobLookupError:
            pass
    def sync_with_scheduler(self):
        if self.is_active:
            self.add_or_update_job()
        else:
            self.remove_job()
        self.update_next_run()
    def update_next_run(self):
        if self.schedule_type == 'cron':
            self.next_run = timezone.now()
        elif self.schedule_type == 'interval':
            self.next_run = self.start_time + self.interval
        elif self.schedule_type == 'date':
            self.next_run = self.start_time
        ScheduledTask.objects.filter(id=self.id).update(
            next_run=timezone.now() if self.schedule_type == 'cron' else
            self.start_time + self.interval if self.schedule_type == 'interval' else
            self.start_time
        )

    def add_or_update_job(self):
        scheduler = get_scheduler()
        job_id = str(self.id)
        # 先检查任务是否存在
        existing_job = scheduler.get_job(job_id)
        if existing_job:
            scheduler.remove_job(job_id)
        # 注册任务
        # 将任务实例和函数作为参数传递
        if self.schedule_type == 'cron':
            scheduler.add_job(
                execute_task_with_logging,
                'cron',
                **self.parse_cron(),
                id=job_id,
                args=[self],  # 将任务实例作为第一个参数传递
                kwargs={
                    'args': self.args,
                    'kwargs': self.kwargs,
                },
                next_run_time=self.start_time
            )
        elif self.schedule_type == 'interval':
            scheduler.add_job(
                execute_task_with_logging,
                'interval',
                **self.parse_interval(),
                id=job_id,
                args=[self],
                kwargs={
                    'args': self.args,
                    'kwargs': self.kwargs,
                },
                start_date=self.start_time
            )
        elif self.schedule_type == 'date':
            if self.start_time < timezone.now():
                raise ValueError("一次性任务的开始时间必须晚于当前时间")
            scheduler.add_job(
                execute_task_with_logging,
                'date',
                misfire_grace_time=60,# 任务执行时间超过预期，容忍60秒的延迟
                coalesce=True,# 如果任务在执行过程中被暂停，则重新执行
                run_date=self.start_time,
                id=job_id,
                args=[self],
                kwargs={
                    'args': self.args,
                    'kwargs': self.kwargs,
                },
                jobstore='default'
            )
            logging.info(f"添加任务成功 ({job_id})") # 生产环境建议换成logging


    def remove_job(self):
        scheduler = get_scheduler()
        job_id = str(self.id)
        if scheduler.get_job(job_id):# 任务存在时才移除
            try:
                scheduler.remove_job(job_id)
                logging.info(f"移除任务成功 ({job_id})") # 生产环境建议换成logging
                print(f"移除任务成功 ({job_id})") # 生产环境建议换成logging
            except JobLookupError:
                # 任务不存在无需处理
                pass
            except Exception as e:
                logging.error(f"移除任务失败 ({job_id}): {str(e)}")

    def parse_cron(self):
        cron_parts = self.cron.split()
        return {
            'minute': cron_parts[0],
            'hour': cron_parts[1],
            'day': cron_parts[2],
            'month': cron_parts[3],
            'day_of_week': cron_parts[4],
        }

    def parse_interval(self):
        interval = self.interval
        return {'seconds': interval.total_seconds()}

    def get_task_function(self):
        """通过任务函数路径返回原始函数，不做包装"""
        module_path, func_name = self.task_function.rsplit('.', 1)
        module = __import__(module_path, fromlist=[func_name])
        return getattr(module, func_name)


def execute_task_with_logging(task_instance, *args, **kwargs):
    """明确的任务执行逻辑，带有记录支持"""
    try:
        # 检查任务是否存在
        if not ScheduledTask.objects.filter(id=task_instance.id).exists():
            logging.warning(f"任务 {task_instance.id} 已被删除，跳过执行")
            # 主动移除调度器中的残留作业
            scheduler = get_scheduler()
            try:
                scheduler.remove_job(str(task_instance.id))
            except JobLookupError:
                pass
            return
        # 获取任务函数
        func = task_instance.get_task_function()
        # 检查任务是否仍然存在
        if not ScheduledTask.objects.filter(id=task_instance.id).exists():
            logging.warning(f"任务 {task_instance.id} 已被删除，跳过执行")
            return
        # 从 kwargs 中提取实际传递给任务的 args 和 kwargs
        actual_args = kwargs.pop('args', [])
        actual_kwargs = kwargs.pop('kwargs', {})

        # 调用实际任务函数
        result = func(*actual_args, **actual_kwargs)

        # 记录成功日志
        TaskExecutionLog.objects.create(
            task=task_instance,
            status="成功",
            result=str(result)
        )
    except Exception as e:
        # 捕获异常并记录失败日志
        TaskExecutionLog.objects.create(
            task=task_instance,
            status="失败",
            error=str(e)
        )
        raise


class TaskExecutionLog(models.Model):
    task = models.ForeignKey(ScheduledTask, on_delete=models.CASCADE, verbose_name='任务')
    execution_time = models.DateTimeField(auto_now_add=True, verbose_name='执行时间')
    status = models.CharField(max_length=10, verbose_name='状态')
    result = models.TextField(blank=True, verbose_name='结果')
    error = models.TextField(blank=True, verbose_name='错误信息')

    def __str__(self):
        return f"{self.task} - {self.status}"


# 初始化调度器
_scheduler = None

def get_scheduler():
    global _scheduler
    if not _scheduler:
        _scheduler = BackgroundScheduler(timezone=settings.TIME_ZONE,job_defaults={'misfire_grace_time': 60})# 任务执行时间超过预期，容忍60秒的延迟
        _scheduler.add_jobstore(DjangoJobStore(), 'default')
        _scheduler.start()
    return _scheduler

# 在models.py末尾添加
from django.db.models.signals import post_delete

def remove_job_handler(sender, instance, **kwargs):
    """确保所有删除操作都会触发任务移除"""
    instance.remove_job()

post_delete.connect(remove_job_handler, sender=ScheduledTask)
